import fs from "fs";
import * as anchor from "@coral-xyz/anchor";
import { PublicKey, Keypair } from '@solana/web3.js';
import * as spl from '@solana/spl-token';
import { hexlify, keccak256 } from 'ethers';
import fetch from 'node-fetch';
import { MessageTransmitter } from '../target/types/message_transmitter';
import { TokenMessengerMinter } from '../target/types/token_messenger_minter';
interface FindProgramAddressResponse {
publicKey: anchor.web3.PublicKey;
bump: number;
}

// Helper function to find PDAs (https://www.anchor-lang.com/docs/pdas)
const findProgramAddress = (
    label: string,
    programId: PublicKey,
    extraSeeds: (string | number[] | Buffer | PublicKey)[] = null
    ): FindProgramAddressResponse => {
        const seeds = [Buffer.from(anchor.utils.bytes.utf8.encode(label))];
        if (extraSeeds) {
            for (const extraSeed of extraSeeds) {
                if (typeof extraSeed === "string") {
                    seeds.push(Buffer.from(anchor.utils.bytes.utf8.encode(extraSeed)));
                } else if (Array.isArray(extraSeed)) {
                    seeds.push(Buffer.from(extraSeed as number[]));
                } else if (Buffer.isBuffer(extraSeed)) {
                    seeds.push(extraSeed);
                } else {
                    seeds.push(extraSeed.toBuffer());
                }
            }
        }
        const res = PublicKey.findProgramAddressSync(seeds, programId);
        return { publicKey: res[0], bump: res[1] };
};

const hexToBytes = (hex: string): number[] => {
    const bytes: number[] = [];
    for (let c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16));
    return bytes;
};

const evmAddressToBytes32 = (address: string): string =>
    `0x000000000000000000000000${address.replace("0x", "")}`;
// Fetches attestation from attestation service given the messageHash
const getAttestationSignature = async (messageHash: string) => {
    console.log("Fetching attestation...");
    let attestationResponse = {status: 'pending'}
    while(attestationResponse.status != 'complete') {
        const response = await
        fetch(`https://iris-api-sandbox.circle.com/attestations/${messageHash}`);
        attestationResponse = await response.json();
        // Wait 2 seconds to avoid getting rate limited
        if (attestationResponse.status !== 'complete') {
            await new Promise(r => setTimeout(r, 2000))
        }
    }
    return attestationResponse.attestation;
}

// Main script
const main = async () => {
    // Configure client to use the provider.
    // Must set ANCHOR_WALLET (solana keypair path) and ANCHOR_PROVIDER_URL (node URL) env vars
    const provider = anchor.AnchorProvider.env();
    anchor.setProvider(provider);
    // Initialize contracts
    const messageTransmitterProgram = anchor.workspace.MessageTransmitter as anchor.Program<MessageTransmitter>; 
    const tokenMessengerMinterProgram = anchor.workspace.TokenMessengerMinter as anchor.Program<TokenMessengerMinter>;
    // Keypair and variables
    const userKeypair = Keypair.fromSecretKey(
    Uint8Array.from(JSON.parse(fs.readFileSync("local_key.json", "utf8")))
    );
    const usdcAddress = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
    const userTokenAccount = process.env.USER_TOKEN_ACCOUNT;
    // Default to 1 USDCSOL (e.g. $0.000001)
    const amount = new anchor.BN(process.env.AMOUNT ?? 1);
    // Default to 1 - AVAX
    const destinationDomain = Number(process.env.DEST_DOMAIN ?? 1);
    // mintRecipient is a bytes32 type so pad with 0's then convert to a solana PublicKey
    const mintRecipient = new PublicKey(hexToBytes(evmAddressToBytes32(process.env.MINT_RECIPIENT_HEX)));

    // Find pdas
    const messageTransmitterAccount = findProgramAddress("message_transmitter",
    messageTransmitterProgram.programId);
    const tokenMessenger = findProgramAddress("token_messenger",
    tokenMessengerMinterProgram.programId);
    const tokenMinter = findProgramAddress("token_minter",
    tokenMessengerMinterProgram.programId);
    const localToken = findProgramAddress("local_token",
    tokenMessengerMinterProgram.programId, [usdcAddress]);
    const remoteTokenMessengerKey = findProgramAddress("remote_token_messenger",
        tokenMessengerMinterProgram.programId, [
            destinationDomain.toString(),
    ]);
    const authorityPda = findProgramAddress("sender_authority",
    tokenMessengerMinterProgram.programId);

     // Call depositForBurn using the Anchor library.
     // userKeypair must have some SOL for gas and USDCSOL in their USDC account
     const depositForBurnTx = await tokenMessengerMinterProgram.methods
// depositForBurn parameters
.depositForBurn({
amount,
destinationDomain,
mintRecipient,
}).accounts({
    owner: userKeypair.publicKey,
    senderAuthorityPda: authorityPda.publicKey,
    burnTokenAccount: userTokenAccount,
    messageTransmitter: messageTransmitterAccount.publicKey,
    tokenMessenger: tokenMessenger.publicKey,
    remoteTokenMessenger: remoteTokenMessengerKey.publicKey,
    tokenMinter: tokenMinter.publicKey,
    localToken: localToken.publicKey,
    burnTokenMint: usdcAddress,
    messageTransmitterProgram: messageTransmitterProgram.programId,
    tokenMessengerMinterProgram: tokenMessengerMinterProgram.programId,
    tokenProgram: spl.TOKEN_PROGRAM_ID,
}).signers([userKeypair]).rpc();

console.log("depositForBurn tx hash: ", depositForBurnTx);
// Fetch transaction, wait 5 seconds first so it can get picked up
await new Promise(r => setTimeout(r, 5000));
const tx = await anchor.getProvider().connection.getTransaction(depositForBurnTx,
{
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
// Decode message field from MessageSent event
const parser = new anchor.EventParser(messageTransmitterProgram.programId, messageTransmitterProgram.coder);
const logs = [...parser.parseLogs(tx.meta.logMessages, false)];
const messageBytes = logs[0].data.message as Buffer;
const message = hexlify(messageBytes);
console.log("depositForBurn message:", message);
// Calculate messageHash to call attestation service
const messageHash = keccak256(messageBytes);
console.log("depositForBurn messageHash: ", messageHash);
// Fetch attestation
const attestation = await getAttestationSignature(messageHash);
console.log("depositForBurn attestation:", attestation);
// Now, you can call receiveMessage on an EVM chain, see public quickstart for more information:
// https://developers.circle.com/stablecoin/docs/cctp-usdc-transfer-quickstart
}
main();
